Pro ASP.NET Core MVC2(第7版)翻译

第3章:MVC 模式、项目和约定

作者:Adam Freeman 翻译:陈广 日期:2018-8-19


在深入研究 ASP.NET MVC 的细节之前,我想确保您熟悉 MVC 设计模式、它背后的思想以及将其转换为ASP.NET Core MVC项目的方式。您可能已经知道我在本章中讨论的一些想法和约定,特别是如果您已经做过 ASP.NET 或 C# 高级开发。如果不是,我鼓励您仔细阅读,很好地理解 MVC 背后的原理可以帮助您在继续阅读这本书时将框架的特性放到上下文中。

MVC 的历史

模型-视图-控制器这个术语从上世纪 70 年代末开始使用,起源于施乐 PARC 的 Smalltalk 项目,它被认为是组织一些早期 GUI 应用程序的一种方式。原始 MVC 模式的一些细节与 Smalltalk 特有的概念(如 screens 和 tools)联系在一起,但更广泛的概念仍然适用于应用程序,它们特别适合于 Web 应用程序。

理解 MVC 模式

从高层次的角度来看,MVC 模式意味着 MVC 应用程序至少将被分成三个部分。

  • Models(模型):包含或表示用户使用的数据
  • Views(视图):用于将模型的某些部分渲染为用户界面
  • Controllers(控制器):处理传入的请求,对模型执行操作,并选择要呈现给用户的视图。

MVC 体系结构的每一个部分都是定义良好的、自包含的,这被称为关注点的分离。在模型中操作数据的逻辑只包含在模型中,显示数据的逻辑只包含在视图中,处理用户请求和输入的代码只包含在控制器中。在每个部分之间有一个清晰的划分,无论应用程序变得多大,在它的整个生命周期中都会更容易维护和扩展。

理解模型

模型 —— 代表 MVC 中的 M,包含用户使用的数据。模型分为两大类型:视图模型(view models) —— 表示从控制器传递到视图的数据;域模型(domain models) —— 包含业务域中的数据,以及用于创建、存储和控制该数据的操作、转换和规则,统称为模型逻辑。

模型是应用程序工作范围的定义。例如,在银行的应用程序中,模型代表应用程序所支持的银行中的所有内容,如帐户、总账和客户信贷限额,以及可用于控制模型中数据的操作,例如存款和从帐户中提取款项。模型还负责保持数据的整体状态和一致性,例如,确保所有事务都被添加到分类账中,并且客户提取的钱不超过他有权提取的或超过银行拥有的钱。

对于 MVC 模式中的每个组件,我将描述哪些应该包含,哪些不应该包含。使用 MVC 模式构建的应用程序时模型应该:

  • 包含域数据
  • 包含创建、管理、更改域数据的逻辑
  • 提供干净的 API用以公开模型数据和操作。

模型不应该:

  • 曝露模型数据获取和管理的细节(换句话说就是不应向控制器和视图公开数据存储机制的细节)
  • 包含基于用户交互的模型转换的逻辑(因为这是控制器的任务)
  • 包含用于向用户显示数据的逻辑(这是视图的任务)

确保模型与控制器和视图隔离的好处是,您可以更方便地测试逻辑(我在第7章中描述了单元测试),并且使增强和维护整个应用程序变得更加简单和容易。

提示:许多刚开始使用 MVC 模式的开发者混淆了在数据模型中包含逻辑的想法,认为 MVC 模式的目标是将数据从逻辑中分离出来。这是一个误解:MVC 模式的目标是将应用程序划分为三个功能区域,每个功能区域都可能包含逻辑和数据。其目的不是消除模型中的逻辑,而是确保模型只包含用于创建和管理模型数据的逻辑。

理解控制器

控制器是 MVC 模式中的连接组织,充当数据模型和视图之间的管道。控制器定义 action,提供在数据模型上运行的业务逻辑,并提供将视图显示给用户的数据。

使用 MVC 模式构建控制器应当:

  • 包含基于用户交互的模型更新所需的操作。

控制器不应该:

  • 包含管理数据显示的逻辑(那是视图的工作)
  • 包含管理数据持久性的逻辑(那是模型的工作)

理解视图

视图包含的逻辑有:向用户显示数据,或从用户获取数据以便由控制器的 action 进行处理。视图应当:

  • 包含向用户展示数据所需的逻辑和标记。

视图不应该:

  • 包含复杂逻辑(最好放在控制器)
  • 包含创建、存储或控制域模型的逻辑

视图可包含逻辑,但它应当简单且较少使用。除简单的方法调用和表达式之外,在视图中放置任何东西都会使整个应用程序变得更难测试和维护。

MVC 的 ASP.NET 实现

顾名思义,ASP.NET Core MVC 将抽象的 MVC 模式应用于 ASP.NET 和 C# 开发领域。在 ASP.NET Core MVC 中,控制器是 C# 类,通常继承自Microsoft.AspNetCore.Mvc.Controller类。每个派生于Controller的类的公共方法都是一个 action 方法。当请求被发送到与 action 方法相关联的 URL 时,执行该 action 方法中的语句是为了对域模型执行某些操作,然后选择要显示给客户端的视图。图3-1显示了控制器、模型和视图之间的交互.

图3-1 MVC 应用程序中的交互

ASP.NET Core MVC 使用了一个名为 Razor 的视图引擎,它是负责处理视图以便为浏览器生成响应的组件。Razor 视图是包含 C# 逻辑的 HTML 模板,用于处理模型数据以生成响应模型更改的动态内容。我在第5章中解释了 Razor 是如何工作的。

ASP.NET Core MVC 不对域模型的实现施加任何约束。您可以使用常规 C# 对象创建模型,还可以使用任何数据库、对象关系映射框架或 .NET 支持的其他数据工具实现数据持久性。

理解单页应用程序

web 应用程序开发的历史倾向于将浏览器视为一种简单的显示设备,用于呈现 HTML 并响应鼠标单击。这被称为往返式 web 应用程序。每次用户单击链接时,都会向 ASP.NET Core MVC 应用程序发送 HTTP 请求,然后控制器选择由 Razor 渲染的视图并将其发送回浏览器,以向用户显示一个新的 HTML 页面。所有的逻辑、数据和状态都驻留在 ASP.NET Core MVC 服务器中,这简化了开发,意味着除了确保浏览器能够处理 Razor 视图中包含的 HTML 特性之外,您不必过多地关注浏览器。

相比之下,单页应用程序将浏览器包含到应用程序平台中。服务器负责管理应用程序的数据,而浏览器则运行 JavaScript 代码以请求该数据,将其显示给用户,并响应用户交互。在单页应用程序中,模型、视图和控制器的责任由浏览器和服务器共同分担。应用程序的 ASP.NET Core MVC 部分提供了对应用程序数据的访问,而不是向浏览器发送完整的 HTML 页面,这些数据由 JavaScript 框架(如 angular 或 react)显示。

单页应用程序比往返式应用程序更有响应性,但创建它们更为复杂,并且需要 C# 和 JavaScript 技能才能进行有效开发,这方面的困难不应低估。在第20章中,我演示了如何使用 ASP.NET Core MVC 在这类应用程序中提供数据,但我没有演示单页应用程序的开发,这本身就是一个主题。我曾在 Pro Angular 中说过我喜欢的 JavaScript 框架是 Angular。如果您想在 ASP.NET Core MVC 中使用 Angular,那么请参阅我的书《Essential Angular for ASP.NET Core MVC》。

MVC 与其它模式的比较

当然,MVC 不是唯一的软件体系结构模式。还有很多其他的,其中一些是非常受欢迎的。通过查看备选方案,您可以了解到许多关于 MVC 的知识。在下面的部分中,我简要地描述了构造应用程序的不同方法,并将它们与 MVC 进行了对比。其中一些模式是 MVC 主题的近亲变体,而另一些则完全不同。

我并不是说 MVC 是所有情况下的完美模式。我赞成选择最合适的方法来解决眼前问题。正如您将看到的那样,在某些情况下,一些竞品模式与 MVC 一样有用或更好。我鼓励您在选择模式时做出明智和慎重的选择。您正在阅读这本书的事实表明,您已经对 MVC 模式有了一定的期许,但保持尽可能广泛的视角总是有帮助的。

理解 Samrt UI 模式

最常见的设计模式之一是 smart user interface(Smart UI)。大多数程序员在职业生涯中的某个时候都创建了 Smart UI 应用程序 —— 我当然也有过。如果您使用过 Windows 窗体或 ASP.NET Web Forms,也是如此。

要构建 smart UI 应用程序,开发者在构建用户界面时通常将一组组件或控件拖到设计界面或画板上。这些控件通过为按下按钮、击键、鼠标移动等发出事件来报告与用户的交互。开发人员在一系列事件处理程序中添加响应这些事件的代码;当特定组件发出特定事件时,这些小代码块将被调用。这将创建一个单片应用程序,如图3-2所示。处理用户界面和业务的代码都混合在一起,关注点完全不分离。定义数据输入的可接受值、查询数据或修改用户帐户的代码以小块结尾,通过事件预期的顺序耦合在一起。

图3-2 smart UI 模式

Smart UI 是简单项目的理想选择,因为您可以快速获得一些好的结果(与MVC开发相比。您将在第8章中看到,在交付结果之前,首先需要投入大量时间)。Smart UI 也适合于用户界面原型。这些设计图形的工具可能非常好,如果您正与客户坐在一起,并且希望获取界面外观和流程的需求,Smart UI 工具可以是生成和测试不同想法的快速响应方式。

Smart UI 的最大的缺点是很难维护和扩展。将域模型、业务逻辑代码与用户界面代码混合在一起会导致重复,其中相同的业务逻辑片段被复制和粘贴以支持新添加的组件。找到所有重复的部件并应用修复是很困难的。如果不破坏现有功能,几乎不可能添加新功能。测试一个 Smart UI 应用程序也可能是困难的。唯一的方法是模拟用户交互,这和理想状态有相当远的距离,也难于提供完整的测试覆盖率。

在 MVC 的世界里,Smart UI 通常被称为反模式:应该不惜一切代价避免的事情。这种反感的产生,至少部分原因是人们在职业生涯的一部分试图开发和维护失去控制的 Smart UI 应用程序之后,来到 MVC 寻找另一种选择。

尽管如此,将 Smart UI 模式拒之门外是一个错误。在智能UI模式中,并不是所有的东西都是不好的,而且这种方法也有可取之处。Smart UI 应用程序开发速度快,且易于开发。组件和设计工具生产者已经花费了大量的精力使开发体验变得愉快,即便是最缺乏经验的程序员也可以在短短几个小时内生产出一些专业的、功能合理的东西。

Smart UI 应用程序的最大弱点 —— 可维护性 —— 不会出现在小的开发工作中。如果您正在为一个小用户生产一个简单的工具,那么 Smart UI 应用程序可能是一个很好的解决方案。MVC 应用程序的额外复杂性是没有必要的。

理解模型-视图体系结构

smart UI 应用程序中可能会出现维护问题的领域是业务逻辑,它最终会在应用程序中扩散开来,以致于进行更改或添加功能成为一个棘手的过程。模型-视图体系结构提供了这方面的改进,它将业务逻辑提取到一个单独的域模型中。这样做时,数据、进程和规则都集中在应用程序的一个部分,如图3-3所示

图3-3 模型-视图模式

模型-视图体系结构可以是对单片 smart UI 模式的改进,例如维护起来容易得多,但是存在两个问题。第一个问题是,由于 UI 和域模型紧密集成,因此很难在其中执行单元测试。第二个问题来自于实践,而不是模式的定义。模型通常包含大量的数据访问代码 —— 并非一定哪些,但通常是这样的 —— 这意味着数据模型不只是包含业务数据、操作和规则。

理解经典的三层体系结构

为了解决模型-视图体系结构的问题,three-tierthree-layer模式(两个中文都译为三层)将持久化代码从域模型中分离出来,并将其放置在一个名为数据访问层(DAL)的新组件中,如图3-4所示。

图3-4 模型-视图模式

三层体系结构是业务应用程序使用最广泛的模式。它对用户界面的实现没有任何限制,并且在不会变得太复杂的情况下提供了很好的关注点分离。而且在一些谨慎的情况下,可以创建 DAL使单元测试变得相对容易。您可以看到经典的三层应用程序与 MVC 模式之间的明显相似之处。区别在于,当 UI 层直接耦合到单击和事件 GUI 框架(如 Windows 窗体或 ASP.NET Web Forms)时,几乎不可能执行自动单元测试。而且,由于三层应用程序的 UI 部分可能很复杂,所以有许多代码无法进行严格的测试。

在最坏的情况下,三层模式缺乏 UI 层的强制纪律,这意味着许多这样的应用程序最终都是伪装得很细的 smart UI 应用程序,没有真正的关注点分离。这给出了最糟糕的结果:一个不可测试的、不可维护的、过于复杂的应用程序。

理解 MVC 变体

我已经描述了 MVC 应用程序的核心设计原则,特别是当它们应用于 ASP NET Core MVC 时。其他人则以不同的方式解释模式的各个方面,并添加、调整或以其他方式修改 MVC,以适应其项目的范围和主题。下面我简要地概述 MVC 主题的两个最流行的变体。理解这些变体对于使用 ASP.NET Core MVC并不重要,为了完整起见,我已经包含了这些信息,您将听到的是大多数关于软件模式的讨论中使用的术语。

理解 Model-View-Presenter 模式

Model-view-presenter (MVP)是 MVC 的一个变体,它更易于适应有状态 GUI 平台,如 Windows 窗体或 ASP.NET Web Forms。这是一次有价值的尝试,它获得 smart UI 模式最好的方面,而避免了 smart UI 带来的问题。

在这种模式中,Presenter 与 MVC 控制器具有相同的责任,但也与有状态视图存在着更直接的关系,根据用户的输入和操作直接管理 UI 组件中显示的值。该模式有两种实现。

  • passive view(被动视图) 实现:视图中不包含任何逻辑。视图是由 Presenter 直接操作的 UI 控件的容器。
  • supervising controller(监视控制器) 实现:视图可能负责某些元素的表示逻辑,如数据绑定,并且已经从域模型中获得了对数据源的引用。

这两种方法的区别在于视图的智能性。无论哪种方法,Presenter 都与 GUI 框架分离,这使得 Presenter 逻辑更简单,适合于单元测试。

理解 Model-View-View Model 模式

Model-View-View Model (MVVM)模式是 MVC 上的一个新变体。它起源于微软,并用于 Windows Presentation Foundation (WPF)。在 MVVM 模式中, Model 和 View 具有与 MVC 中相同的角色。不同之处在于 MVVM 中的 view model 概念,它是用户界面的抽象表示。通常,view model 是一个 C# 类,它公开了 UI 中显示的数据的属性和可以从 UI 调用的数据的操作。与 MVC 控制器不同,MVVM 的 view model 不存在 view 的概念(或任何特定的UI技术)。MVVM 的 view 使用 WPF 绑定功能将 view 中控件公开的属性(下拉菜单中的项或按下按钮的效果)与 view model 公开的属性双向关联。

提示:MVC 模式也使用了术语 view model,但它指的是一个简单的模型类,它只用于将数据从控制器传递到视图,而不是域模型,后者是数据、操作和规则的复杂表示。

理解 ASP.NET Core MVC 项目

创建新的 ASP.NET Core MVC 项目时,Visual Studio 将为您提供有关项目初始内容的一些选择。这样做的目的是简化初学者的学习过程,并为常见的功能和任务应用一些节省时间的最佳实践。我不喜欢这种方式的曲奇切分项目或代码。意图是好的,但执行是平淡无奇的。关于ASP.NET Core 和 MVC,我最喜欢的特点之一就是在调整平台以适应我的开发风格方面的灵活性。Visual Studio 创建和填充的项目、类和视图使我感到不得不以其他人的风格工作。我还发现内容和配置过于通用和平淡没有多大用处。微软不可能知道需要什么样的应用程序,所以它涵盖了所有的基础,但是以这样一种广义的方式,我最终还是会删除默认的内容。

我的建议(给那些犯了错误的人)是从一个空项目开始,并添加您需要的文件夹、文件和包。不仅可以了解 MVC 的工作方式,而且可以完全控制应用程序所包含的内容。

但是我的偏好不应该影响您的开发经验。您可能会发现这些模板比我更有用,特别是如果您是 ASP.NET 开发新手,而且还没有找到适合您的开发风格。您可能还会发现项目模板是一种有用的资源和思想来源,尽管在您完全了解应用程序如何工作之前,应该谨慎地将任何功能添加到应用程序中。

创建一个项目

在第一次创建一个新的 ASP.NET Core 项目时,您将看到一系列起点,如图3-5所示。 图3-5 ASP.NET Core 项目模板

【Empty】项目模板包含 ASP.NET Core 的管道,但不包括 MVC 应用程序所需的库或配置。【Web API】项目模板包括 ASP.NET Core 和 MVC,其中包含一个示例应用程序,演示如何使用 API 控制器接收和处理来自客户端的数据请求,我在第20章中对此进行了描述。

【Web Application (Model-View-Controller)】项目模板包括 ASP.NET Core 和 MVC,以及演示如何生成 HTML 的示例应用程序。【Web API】和【Web Application(Model-View-Controller)】模板可以配置不同的方案来验证用户并授权他们访问应用程序。

其他模板提供的初始内容适合于使用单页应用程序框架(Angular 和 React)和 Razor 页(这允许代码和标记混合在一个文件中,合并控制器和视图的角色,并且为了简单起见而牺牲 MVC 模型的一些好处)。

项目模板可能给人的印象是,需要遵循特定的路径来创建某种类型的 ASP.NET Core 应用程序,但事实并非如此。模板只是不同的起始点到相同的功能,您可以给任何模板所创建的项目添加所需的任何功能。例如,我在第20章中解释了如何处理 HTTP 数据请求,在第28、29和30章中解释了身份验证和授权,所有这些都是从【Empty】项目模板开始的。

项目模板之间真正的区别在于创建项目时 Visual Studio 所添加的库、配置文件、代码和内容的初始集合。最简单的模板【Empty】和最复杂的模板【Web Application (Model-View-Controller)】之间有很多不同,图3-6显示了在每个项目创建之后的【Solution Explorer】。对于【Web Application (Model-View-Controller)】模板,我必须使用多张图片显示,因为单张图片对于页面来说太长了。 图3-6 使用 Empty 和 Web Application (MVC) 模板创建项目时的默认内容

【Web Application (Model-View-Controller)】模板添加到项目中的额外文件看起来令人望而生畏,但其中许多文件只是占位符或通用功能的示例实现。其他一些文件设置 MVC 或配置 ASP.NET Core。其他文件是客户端库,您将把这些库合并到应用程序生成的 HTML 中。现在,文件列表看起来可能是巨大的,但是当你读完这本书的时候,你就会明白它们都做了些什么。

不管用于创建项目的模板是什么,都会出现一些常见的文件夹和文件。项目中的某些项具有特殊角色,它们硬编码到 ASP.NET Core 或 MVC 或 Visual Studio 所支持的工具中。其他则受大多数 ASP.NET Core 或 MVC 项目中使用的命名约定的限制。在表3-1中描述了在 ASP.NET Core MVC 项目中会遇到的重要文件和文件夹,其中一些在默认情况下不存在于项目中,但我将在后面的章节中介绍。

表 3-1:MVC 项目中的项摘要

文件或文件夹 描述
/Areas Areas 是将大型应用程序划分为较小部分的一种方法。我将在第16章中描述 Areas。
/Dependencies Dependencies 中的项提供了项目依赖的所有包的详细信息。我在第6章中描述了 Visual Studio 使用的包管理器。
/Components 这是视图组件类,用于显示诸如购物车之类的自包含功能。我在第22章中描述视图组件。
/Controllers 这里存放控制器类。这是约定。可以把控制器类放在任何你喜欢的地方,因为它们都被编译成同一个程序集。我将在第17章详细描述控制器。
/Data 这里是定义数据库 context 类的地方,尽管我倾向于忽略这个约定并在 Models 文件夹中定义它们,如第8章所示
/Data/Migrations 这是存储Entity Framework Core迁移的地方,以便数据库可以准备存储应用程序数据。我在8、9、10和11章使用了迁移作为 SportsStore 项目的一部分
/Models 这里存放视图模型和域模型类。这是约定。您可以在项目中的任何地方或在单独的项目中定义模型类。
/Views 此目录保存视图和分部视图,通常按与其关联的控制器命名的文件夹分组。我在第21章中详细描述了视图。
/Views/Shared 此目录保存不专属于单个控制器的布局和视图。我在第21章中详细描述了视图。
/Views/_ViewImports.cshtml 如第5章所述,此文件用于指定将包含在 Razor 视图文件中的命名空间。如第23章所述,它还用于设置标签助手。
/Views/_ViewStart.cshtml 此文件用于指定 Razor 视图引擎的默认布局,如第5章所述。
/appsettings.json 此文件包含可以针对不同环境定制的配置,例如开发、测试和生产。它最常见的用途是定义数据库服务器连接字符串和 日志/调试 设置,将在14章中描述。
/bower.json 如第6章所述,此文件包含 Bower 包管理器管理的包列表。
/<project>.csproj 该文件包含项目的配置,包括应用程序所需的 Nuget 包,如第6章和第14章所述。此文件是隐藏的,只能通过右键单击【Solution Explorer】窗口中的项目并选择【Edit <project>.csproj】菜单项来编辑。
/Program.cs 这个类配置应用程序的托管平台,如第14章所述。
/Startup.cs 这个类配置应用程序,如第14章所述。
/wwwroot 这是放置静态内容的地方,如CSS文件和图片,也是 Bower 包管理器安装 JavaScript 和CSS包的地方,如第6章所述。

理解 MVC 约定

MCV 项目有两类约定,第一类是关于如何组织项目的建议。例如,将所依赖的第三方 JavaScript 和 CSS 包放在 wwwroot/lib 文件夹就是约定的做法。这是其他 MVC 开发人员希望找到它们的地方,也是包管理器安装它们的地方。但是您可以重新命名 lib 文件夹,或者完全删除它,并将包放在其他地方。这不会阻止 MVC 运行应用程序,只要在视图中的脚本和链接元素引用您确定的位置。

另一类约定源自于惯例优先(convention over configuration)原则,这是 Ruby on Rails 受欢迎的主要卖点之一。惯例优先原则意味着不需要显式配置控制器与其视图之间的关联。例如,您只需对您的文件遵循特定的命名约定,一切就会正常工作。在处理此类约定时,更改项目结构的灵活性较低。以下部分将解释用于代替配置的约定。

提示:所有的约定都可以通过将标准 MVC 组件替换为自己实现的来改变。我在书中讲述了实现这件事的不同方法,以帮助解释 MVC 应用程序是如何工作的,但这些是您在大多数项目中要面对的约定。

控制器类遵循的约定

控制器类的名称以 Controller 结尾,如 ProductController、AdminController 和 HomeController。当从项目的其他地方引用控制器时,例如当使用 HTML 助手方法时,可以指定名称的第一部分(例如 Product),MVC 会自动将 Controller 追加到名称后面,并开始查找控制器类。

提示:您可以通过创建一个模型约定来改变这一点,我将在第31章中对此进行描述。

视图遵循的约定

视图放置在名为【/Views/控制器名称】的文件夹。例如一个以ProductController类相关联的视图应该放在【/Views/Product】文件夹下。

提示:注意,我省略了类中的Controller部分,并非【/Views/ProductController】,起初,这看上去似乎有悖常理,但很快就变成了第二天性。

MVC 期望 action 方法的默认视图应该以该方法命名。例如,一个名为List的 action 方法关联的默认视图的名称应为 List.cshtml。因此,对于 ProductController 类中的List action方法,默认视图应该是【/Views/Product/List.cshtml】。当在一个 action 方法中返回的是View方法,则使用默认视图,如下所示:

...
return View();
...

你可以通过名称指定不同的视图,如下:

...
return View("MyOtherView");
...

注意,并没包括文件扩展名或视图的路径。在寻找一个视图时,MVC 在以控制器命名的文件夹中查找,然后在 /Views/Shared 文件夹中查找。这意味着我可以将视图添加到 /Views/Shared 文件夹中,以便由多个控制器使用,MVC 将找到它们。

布局遵循的约定

布局的命名约定是在文件的前缀加上下划线_字符,布局文件放在 /Views/Shared 文件夹中。默认情况下,此布局通过 /Views/_ViewStart.cshtml 文件应用于所有视图。如果不希望默认布局应用于视图,可以更改 _ViewStart.cshtml 中的设置(或完全删除文件),以指定视图中的另一个布局,如下所示:

@{
    Layout = "~/_MyLayout.cshtml";
}

也可以禁用给定视图的任何布局,如下所示:

@{
    Layout = null;
}

总结

本章向您介绍了 MVC 体系结构模式,并将其与以前可能见过或听说过的其他一些模式进行了比较。我讨论了域模型的重要性,并引入了依赖注入,它允许您将组件解耦,以强制在应用程序的各个部分之间进行严格的分离。下一章,我将讨论在 MVC web 应用程序开发中使用到的 C# 语言的基本特性。

;

© 2018 - IOT小分队文章发布系统 v0.3